﻿// Copyright (c) 2014 Morten Bakkedal
// This code is published under the MIT License.

using System;

namespace FuncLib.DualFunctions.Collections
{
	public static class HashedTuple
	{
		public static HashedTuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2)
		{
			return new HashedTuple<T1, T2>(item1, item2);
		}
	}

	/// <summary>
	/// Tuple class with precomputed hash code.
	/// </summary>
	[Serializable]
	public class HashedTuple<T1, T2> //: Tuple<T1, T2> - override GetHashCode(), Equals() (using hash code)?
	{
		private T1 item1;
		private T2 item2;
		private int hashCode;

		public HashedTuple(T1 item1, T2 item2)
		{
			this.item1 = item1;
			this.item2 = item2;

			hashCode = 23;
			unchecked
			{
				hashCode = hashCode * 31 + item1.GetHashCode();
				hashCode = hashCode * 31 + item2.GetHashCode();
			}
		}

		public override int GetHashCode()
		{
			return hashCode;
		}

		public override bool Equals(object other)
		{
			return Equals(other as HashedTuple<T1, T2>);
		}

		private bool Equals(HashedTuple<T1, T2> other)
		{
			// Follows Item 6 in Effective C# in first three tests.

			if (object.ReferenceEquals(other, null))
			{
				return false;
			}

			if (object.ReferenceEquals(this, other))
			{
				return true;
			}

			if (GetType() != other.GetType())
			{
				return false;
			}

			if (hashCode != other.hashCode)
			{
				// Can't be equal if they have different hash codes.
				return false;
			}

			//return item1.Equals(other.item1) && item2.Equals(other.item2);
			return object.Equals(item1, other.item1) && object.Equals(item2, other.item2);
		}

		public static bool operator ==(HashedTuple<T1, T2> tuple1, HashedTuple<T1, T2> tuple2)
		{
			return object.Equals(tuple1, tuple2);
		}

		public static bool operator !=(HashedTuple<T1, T2> tuple1, HashedTuple<T1, T2> tuple2)
		{
			return !(tuple1 == tuple2);
		}

		public T1 Item1
		{
			get
			{
				return item1;
			}
		}

		public T2 Item2
		{
			get
			{
				return item2;
			}
		}
	}
}
